package org.meetunexpectedly.service.video.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.meetunexpectedly.config.LocalCache;
import org.meetunexpectedly.config.QiNiuConfig;
import org.meetunexpectedly.constant.AuditStatus;
import org.meetunexpectedly.constant.RedisConstant;
import org.meetunexpectedly.entity.task.VideoTask;
import org.meetunexpectedly.entity.user.FavoritesVideo;
import org.meetunexpectedly.entity.user.User;
import org.meetunexpectedly.entity.video.*;
import org.meetunexpectedly.entity.vo.BasePage;
import org.meetunexpectedly.entity.vo.HotVideo;
import org.meetunexpectedly.entity.vo.UserModel;
import org.meetunexpectedly.entity.vo.UserVO;
import org.meetunexpectedly.exception.BaseException;
import org.meetunexpectedly.holder.IPHolder;
import org.meetunexpectedly.holder.UserHolder;
import org.meetunexpectedly.mapper.video.VideoMapper;
import org.meetunexpectedly.schedul.HotRank;
import org.meetunexpectedly.service.audit.impl.VideoPublishAuditServiceImpl;
import org.meetunexpectedly.service.file.FileService;
import org.meetunexpectedly.service.user.*;
import org.meetunexpectedly.service.video.*;
import org.meetunexpectedly.util.FileUtil;
import org.meetunexpectedly.util.JwtUtils;
import org.meetunexpectedly.util.RedisCacheUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Description: 视频服务实现类
 * @Author: pupil
 * @Date: 2024/08/17 下午 6:30
 */
@Service
public class VideoServiceImpl extends ServiceImpl<VideoMapper, Video> implements VideoService {
    // 标签最大限制数
    private static final int TYPE_LIMIT = 5;
    // 获取的视频数
    private static final int VIDEO_COUNT = 10;
    // 根据视频分类获取视频 数
    private static final int VIDEO_TYPE_COUNT = 15;

    @Autowired
    private TypeService typeService;

    @Autowired
    private FileService fileService;

    @Autowired
    private VideoPublishAuditServiceImpl videoPublishAuditService;

    @Autowired
    private InterestPushService interestPushService;

    @Autowired
    private VideoShareService videoShareService;

    @Autowired
    private VideoThumbService videoThumbService;

    @Autowired
    private FavoritesService favoritesService;

    @Autowired
    private FavoritesVideoService favoritesVideoService;

    @Autowired
    private FollowService followService;

    @Autowired
    private UserService userService;

    @Autowired
    private FeedService feedService;

    @Autowired
    @Lazy
    private HotRank hotRank;

    @Autowired
    private RedisCacheUtil redisCacheUtil;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private VideoMapper videoMapper;

    private final ObjectMapper objectMapper = new ObjectMapper();


    @Override
    public void publishVideo(Video video) {
        Long userId = UserHolder.get();
        Long videoId = video.getId();
        // 判断是否是新添加
        boolean isAdd = true;
        Video oldVideo = null;
        if (videoId != null) {
            isAdd = false;
            // 获取旧的视频信息
            oldVideo = this.getOne(new LambdaQueryWrapper<Video>().eq(Video::getId, videoId).eq(Video::getUserId, userId));
            if (!video.buildVideoUrl().equals(oldVideo.buildVideoUrl()) || !video.buildCoverUrl().equals(oldVideo.buildCoverUrl())) {
                throw new BaseException("不能更换视频源和封面源，只能修改视频信息");
            }
        }

        // 判断对应的分类是否存在
        Type type = typeService.getById(video.getTypeId());
        if (type == null) {
            throw new BaseException("分类不存在");
        }

        // 校验标签最多不能超过5个
        if (video.buildLabel().size() > TYPE_LIMIT) {
            throw new BaseException("标签最多选择" + TYPE_LIMIT + "个");
        }

        // 修改审核状态：审核中
        video.setAuditStatus(AuditStatus.PROCESS.num);
        video.setMsg(AuditStatus.PROCESS.name);
        // 设置发布者id
        video.setUserId(userId);

        // 校验
        if (!isAdd) {
            video.setYv(null);
            video.setUrl(null);
            video.setCover(null);
        } else {
            // 如果没设置封面,我们帮他设置一个封面
            if (ObjectUtils.isEmpty(video.getCover())) {
                video.setCover(fileService.generatePhoto(video.getUrl(), userId));
            }
            // 设置视频号
            video.setYv("YV" + UUID.randomUUID().toString().replace("-", "").substring(8));
        }

        // 填充视频时长 (若上次发布视频不存在Duration则会尝试获取)
        if (isAdd || !StringUtils.hasLength(oldVideo.getDuration())) {
            String uuid = UUID.randomUUID().toString();
            // 添加本地缓存
            LocalCache.put(uuid, true);
            try {
                Long url = video.getUrl();
                // 修改视频时不会传入url
                if (url == null || url == 0) url = oldVideo.getUrl();
                String fileKey = fileService.getById(url).getFileKey();
                String duration = FileUtil.getVideoDuration(QiNiuConfig.RealmName + "/" + fileKey + "?uuid=" + uuid);
                video.setDuration(duration);
            } finally {
                // 删除缓存
                LocalCache.remove(uuid);
            }
        }

        // 发布或者修改视频
        this.saveOrUpdate(video);

        // 删除旧的热度排行榜
        String hotRankKey = RedisConstant.HOT_RANK;
        redisTemplate.delete(hotRankKey);
        // 更新热度排行榜和热门视频
        this.hotRank.hotRank();
        this.hotRank.hotVideo();

        // 视频审核任务
        VideoTask videoTask = new VideoTask();
        videoTask.setVideo(video);
        videoTask.setOldVideo(oldVideo);
        videoTask.setIsAdd(isAdd);
        videoTask.setOldState(isAdd ? false : oldVideo.getOpen());
        videoTask.setNewState(false);
        // 提交任务进行视频审核(实际的调用)
        videoPublishAuditService.audit(videoTask, false);
    }

    @Override
    public Boolean deleteVideo(Long id) {
        if (id == null) {
            throw new BaseException("删除指定的视频不存在");
        }
        Long userId = UserHolder.get();
        Video video = this.getOne(new LambdaQueryWrapper<Video>().eq(Video::getId, id).eq(Video::getUserId, userId));
        if (ObjectUtils.isEmpty(video)) {
            throw new BaseException("删除指定的视频不存在");
        }
        boolean result = removeById(id);
        if (result) {
            // 解耦
            new Thread(() -> {
                // 删除视频分享量 点赞量
                videoShareService.remove(new LambdaQueryWrapper<VideoShare>().eq(VideoShare::getVideoId, video.getId()).eq(VideoShare::getUserId, video.getUserId()));
                videoThumbService.remove(new LambdaQueryWrapper<VideoThumb>().eq(VideoThumb::getVideoId, video.getId()).eq(VideoThumb::getUserId, video.getUserId()));

                // 把视频从收藏夹删除
                favoritesVideoService.remove(new LambdaQueryWrapper<FavoritesVideo>().eq(FavoritesVideo::getVideoId, video.getId()));

                // 从文件表中删除
                fileService.remove(new LambdaQueryWrapper<File>().eq(File::getId, video.getUrl()));
                // 从文件表中删除视频封面
                fileService.remove(new LambdaQueryWrapper<File>().eq(File::getId, video.getCover()));
                // todo 删除评论 但评论功能还没有完成

                // 该视频从分类库和标签库中删除
                interestPushService.deleteSystemTypeStockIn(video);
                interestPushService.deleteSystemStockIn(video);

                // 从热门视频和热门排行榜视频中删除
                Calendar calendar = Calendar.getInstance();
                int today = calendar.get(Calendar.DATE);
                // 尝试寻找视频进行删除
                // 从3天内的热门视频中寻找
                redisTemplate.opsForZSet().remove(RedisConstant.HOT_VIDEO + today, video.getId());
                redisTemplate.opsForSet().remove(RedisConstant.HOT_VIDEO + (today - 1), video.getId());
                redisTemplate.opsForSet().remove(RedisConstant.HOT_VIDEO + (today - 2), video.getId());
                // 从热度排行榜中删除
                redisTemplate.opsForZSet().remove(RedisConstant.HOT_RANK, video.getId());

                // 从博主的发件箱中删除视频
                Collection<Long> fans = followService.getFans(null, video.getUserId());
                feedService.deleteOutBoxFeed(userId, fans, video.getId());
            }).start();
        }
        return result;
    }

    @Override
    public IPage<Video> getUserVideos(BasePage basePage, Long userId) {
        IPage<Video> page = this.page(basePage.page(), new LambdaQueryWrapper<Video>().eq(Video::getUserId, userId).orderByDesc(Video::getGmtCreated));
        setUserVOAndUrl(page.getRecords());
        return page;
    }

    @Override
    public Boolean thumbUpVideo(Long videoId) {
        Video video = this.getById(videoId);
        if (video == null) {
            throw new BaseException("指定视频不存在");
        }
        VideoThumb videoThumb = new VideoThumb();
        Long userId = UserHolder.get();
        videoThumb.setVideoId(videoId);
        videoThumb.setUserId(userId);

        Boolean result = videoThumbService.thumbUp(videoThumb);
        updateThumbUp(video, result ? 1L : -1L);

        double probability = result ? 2.0 : -1.0;

        // 获取标签
        List<String> labels = video.buildLabel();
        // 视频点赞对应的标签概率+2
        UserModel userModel = UserModel.buildUserModel(labels, videoId, probability);
        // 更新用户模型
        interestPushService.updateUserModel(userModel);

        return result;
    }

    @Override
    public Boolean shareVideo(Long videoId, HttpServletRequest request) {
        String ip = IPHolder.get();
        VideoShare videoShare = new VideoShare();
        videoShare.setVideoId(videoId);
        videoShare.setIp(ip);
        // 判断用户是否登录状态
        if (JwtUtils.checkToken(request)) {
            // 获取用户id
            Long userId = JwtUtils.getUserId(request);
            videoShare.setUserId(userId);
        }

        // 判断视频是否存在
        Video video = this.getById(videoId);
        if (video == null) {
            throw new BaseException("指定视频不存在");
        }


        Boolean result = videoShareService.share(videoShare);
        updateShare(video, result ? 1L : 0L);

        double probability = result ? 2.0 : 0.0;

        // 获取标签
        List<String> labels = video.buildLabel();
        // 视频分享对应的标签概率+2
        UserModel userModel = UserModel.buildUserModel(labels, videoId, probability);
        // 更新用户模型
        interestPushService.updateUserModel(userModel);
        return result;
    }

    @Override
    public Video getVideoById(Long videoId, Long userId) {
        Video video = this.getOne(new LambdaQueryWrapper<Video>().eq(Video::getId, videoId));
        if (ObjectUtils.isEmpty(video)) {
            throw new BaseException("指定视频不存在");
        }

        // 私密视频返回空
        if (video.getOpen()) {
            return new Video();
        }

        setUserVOAndUrl(Collections.singleton(video));
        // 当前视频,用户自己是否有收藏、点赞和关注作者过等信息
        video.setThumb(checkState(RedisConstant.USER_THUMB + userId, video, userId));
        video.setFavorites(checkState(RedisConstant.USER_FAVORITES + userId, video, userId));
        video.setFollow(checkState(RedisConstant.USER_FOLLOWS + userId, video, userId));
        return video;
    }

    @Override
    public Collection<Long> listVideoIdByUserId(Long userId) {
        List<Long> videoIds = this.list(new LambdaQueryWrapper<Video>().eq(Video::getUserId, userId).eq(Video::getAuditStatus, AuditStatus.SUCCESS.num).select(Video::getId)).
                stream().map(Video::getId).collect(Collectors.toList());
        return videoIds;
    }

    @Override
    public IPage<Video> getVideoByUserId(Long userId, BasePage basePage) {
        if (userId == null) {
            return new Page<>();
        }

        IPage<Video> page = this.page(basePage.page(), new LambdaQueryWrapper<Video>().eq(Video::getUserId, userId).eq(Video::getAuditStatus, AuditStatus.SUCCESS.num).orderByDesc(Video::getGmtCreated));
        List<Video> videos = page.getRecords();
        setUserVOAndUrl(videos);
        return page;
    }

    @Override
    public Collection<Video> followFeed(Long userId, Long lastTime) {
        Set<Long> videoIds = redisTemplate.opsForZSet().reverseRangeByScore(RedisConstant.USER_IN_FOLLOW + userId,
                0, lastTime == null ? new Date().getTime() : lastTime,
                lastTime == null ? 0 : 1, 5);

        if (ObjectUtils.isEmpty(videoIds)) {
            // 缓存过期了或者用户没有关注其他用户
            // 缓存中没有，缓存只存储7天内的关注视频，一直往后查看的用户太少，这里不做考虑，一直查看 Feed流肯定会出错
            // todo 这个后续需要根据实际状况进行优化
            return Collections.EMPTY_LIST;
        }


        // 这里不会按照时间排序，需要手动排序
        // 从数据库中查询视频
        Collection<Video> videos = list(new LambdaQueryWrapper<Video>().in(Video::getId, videoIds).orderByDesc(Video::getGmtCreated));

        setUserVOAndUrl(videos);

        return videos;
    }

    @Override
    public void initFollowFeed(Long userId) {
        Collection<Long> followsIds = followService.getFollows(null, userId);
        feedService.initFollowFeed(userId, followsIds);
    }

    @Override
    public String getAuditQueueState() {
        // 实际调用
        return videoPublishAuditService.getAuditQueueState() ? "快速" : "慢速";
    }

    @Override
    public IPage<Video> searchVideo(String searchName, BasePage basePage, Long userId) {
        LambdaQueryWrapper<Video> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Video::getAuditStatus, AuditStatus.SUCCESS.num);
        String YVNum = searchName.substring(0, 2);
        if (YVNum.equals("YV")) {
            // 带YV则精准搜该视频
            wrapper.eq(Video::getYv, searchName);
        } else {
            // 其他的用户模糊搜索
            wrapper.like(!ObjectUtils.isEmpty(searchName), Video::getTitle, searchName);
        }

        IPage<Video> page = this.page(basePage.page(), wrapper);
        List<Video> videos = page.getRecords();
        setUserVOAndUrl(videos);

        // 添加搜索记录
        userService.setSearchHistory(userId, searchName);

        return page;
    }

    @Override
    public Collection<Video> pushVideos(Long userId) {

        User user = userId != null ? userService.getById(userId) : null;

        //  获取推荐视频id，可能为空
        Collection<Long> videoIds = interestPushService.listVideoIdsByUserModel(user);
        Collection<Video> videos = new ArrayList<>();

        // 如果获取的视频id为空，则从数据库中寻找最新的视频
        if (ObjectUtils.isEmpty(videoIds)) {
            // 数据库中查询最新的视频
            videos = this.list(new LambdaQueryWrapper<Video>().eq(Video::getOpen, false).orderByDesc(Video::getGmtUpdated).last("LIMIT " + VIDEO_COUNT));
            Random random = new Random();
            // 随机获取标签
            List<String> labels = typeService.randomLabels(VIDEO_COUNT);
            List<String> labelNames = new ArrayList<>();
            for (String label : labels) {
                labelNames.add(RedisConstant.SYSTEM_STOCK + label);
            }
            Collections.shuffle(labelNames);
            // 获取videoId，已经进行去空处理
            List<Object> videoIdsList = redisCacheUtil.sRandom(labelNames);
            if (!ObjectUtils.isEmpty(videoIdsList)) {
                Set<Long> ids = videoIdsList.stream().map(id -> Long.parseLong(id.toString())).collect(Collectors.toSet());
                videoIds.addAll(ids);
            }
            Collection<Video> randomVideos = this.listByIds(videoIds);
            for (int i = 0; i < randomVideos.size()/2; i++){
                int num = random.nextInt(randomVideos.size());
                randomVideos.remove(num);
            }
            // 从热门视频中查询视频
            Collection<Video> hotVideos = getHotVideos();
            for (int i = 0; i < hotVideos.size()/2; i++){
                int num = random.nextInt(hotVideos.size());
                hotVideos.remove(num);
            }
            videos.addAll(randomVideos);
            videos.addAll(hotVideos);
            videos.stream().filter(video -> !ObjectUtils.isEmpty(video)).collect(Collectors.toSet());
            Collections.shuffle((List<?>) videos);
        } else {
            videos = this.listByIds(videoIds);
        }

        setUserVOAndUrl(videos);

        return videos;
    }

    @Override
    public Collection<Video> getVideoByTypeId(Long typeId) {
        if (typeId == null) {
            return Collections.emptyList();
        }

        Type type = typeService.getById(typeId);
        if (type == null) {
            return Collections.emptyList();
        }

        Collection<Long> videoIds = interestPushService.listVideoIdByTypeId(typeId);

        // 创建结果集
        Collection<Video> videos = Collections.emptyList();
        // 可能redis宕机了，从数据库中获取15个该分类最新的视频
        if (ObjectUtils.isEmpty(videoIds)) {
            videos = this.list(new LambdaQueryWrapper<Video>()
                    .eq(Video::getTypeId, typeId)
                    .eq(Video::getAuditStatus, AuditStatus.SUCCESS.num)
                    .orderByDesc(Video::getGmtUpdated)
                    .last("LIMIT " + VIDEO_TYPE_COUNT));

        } else {
            videos = this.listByIds(videoIds);
        }

        setUserVOAndUrl(videos);

        return videos;
    }

    @Override
    public Collection<Video> getFavoritesVideos(Long favoriteId) {
        List<Long> videoIds = favoritesService.listVideoIds(favoriteId, UserHolder.get());

        if (ObjectUtils.isEmpty(videoIds)) {
            return Collections.emptyList();
        }

        Collection<Video> videos = this.listByIds(videoIds);

        setUserVOAndUrl(videos);
        return videos;
    }

    @Override
    public Boolean favoritesVideo(Long favoriteId, Long videoId) {
        Video video = this.getById(videoId);
        if (video == null) {
            throw new BaseException("指定视频不存在");
        }

        Boolean result = favoritesService.favoritesVideo(favoriteId, videoId);
        updateFavorites(video, result ? 1L : -1L);

        double probability = result ? 3.0 : -1.5;

        // 获取标签
        List<String> labels = video.buildLabel();
        // 视频点赞对应的标签概率+3:-1.5
        UserModel userModel = UserModel.buildUserModel(labels, videoId, probability);
        // 更新用户模型
        interestPushService.updateUserModel(userModel);

        return result;
    }

    @Override
    public LinkedHashMap<String, List<Video>> getHistory(BasePage basePage) {
        Long useId = UserHolder.get();
        String key = RedisConstant.USER_HISTORY_VIDEO + useId;
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisCacheUtil.zSetGetByPage(key, basePage.getPage(), basePage.getLimit());
        if (ObjectUtils.isEmpty(typedTuples)) {
            return new LinkedHashMap<>();
        }

        List<Video> videosTemp = new ArrayList<>();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        // 根据时间进行视频的分类
        LinkedHashMap<String, List<Video>> result = new LinkedHashMap<>();

        for (ZSetOperations.TypedTuple<Object> typedTuple : typedTuples) {
            Date date = new Date(typedTuple.getScore().longValue());
            String format = simpleDateFormat.format(date);
            // 如果这个集合的key不存在，则创建
            if (!result.containsKey(format)) {
                result.put(format, new ArrayList<>());
            }
            // 获取视频，根据时间存储到集合中
            Video video = (Video) typedTuple.getValue();
            result.get(format).add(video);

            videosTemp.add(video);
        }

        setUserVOAndUrl(videosTemp);

        return result;
    }

    @Override
    @Async
    public void addHistory(Long videoId, Long userId) {
        if (userId == null) {
            throw new BaseException("userid不能为空");
        }

        Video video = this.getById(videoId);
        if (video == null) {
            throw new BaseException("指定视频不存在");
        }
        // 记录用户的浏览记录用于兴趣推送的去重
        String key = RedisConstant.HISTORY_VIDEO + videoId + ":" + userId;
        Object o = redisCacheUtil.get(key);
        if (o == null) {
            redisCacheUtil.set(key, videoId, RedisConstant.HISTORY_TIME);
            video.setUser(userService.getInfo(video.getUserId()));
            video.setTypeName(typeService.getById(video.getTypeId()).getName());
            // 添加用户的浏览记录，根据时间进行排序
            redisCacheUtil.zAdd(RedisConstant.USER_HISTORY_VIDEO + userId, new Date().getTime(), video, RedisConstant.HISTORY_TIME);
            updateHistory(video, 1L);
        }

    }

    @Override
    public Collection<HotVideo> getHotRank() {
        Set<ZSetOperations.TypedTuple<Object>> zSet = redisTemplate.opsForZSet().reverseRangeWithScores(RedisConstant.HOT_RANK, 0, -1);
        ArrayList<HotVideo> hotVideos = new ArrayList<>();
        for (ZSetOperations.TypedTuple<Object> typedTuple : zSet) {
            HotVideo hotVideo;
            try {
                hotVideo = objectMapper.readValue(typedTuple.getValue().toString(), HotVideo.class);
                hotVideo.setHot((double) typedTuple.getScore().intValue());
                hotVideo.hotFormat();
                hotVideos.add(hotVideo);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return hotVideos;
    }

    @Override
    public Collection<Video> getHotVideos() {
        // 获取默认时区和区域设置的日历实例
        Calendar calendar = Calendar.getInstance();
        // 获取今天的日期
        int today = calendar.get(Calendar.DATE);

        HashMap<String, Integer> map = new HashMap<>();
        // 优先推送今日的
        map.put(RedisConstant.HOT_VIDEO + today, 10); // 今天的获取10个视频
        map.put(RedisConstant.HOT_VIDEO + (today - 1), 3); // 昨天的获取3个视频
        map.put(RedisConstant.HOT_VIDEO + (today - 2), 2); // 前天的获取2个视频

        // 游客不用记录浏览记录

        // 获取热门视频
        List<List<Long>> hotVideoIds = redisCacheUtil.pipeline(connection -> {
            map.forEach((k, v) -> {
                connection.sRandMember(k.getBytes(), v);
            });
            return null;
        });

        if (ObjectUtils.isEmpty(hotVideoIds)) {
            return Collections.emptyList();
        }

        List<Long> videoIds = new ArrayList<>();
        // 会返回结果有null，做下校验
        for (List<Long> hotVideoId : hotVideoIds) {
            if (!ObjectUtils.isEmpty(hotVideoId)) {
                videoIds.addAll(hotVideoId);
            }
        }

        if (ObjectUtils.isEmpty(videoIds)) {
            return Collections.emptyList();
        }

        Collection<Video> videos = this.listByIds(videoIds);
        // 不需要与浏览记录进行去重，热门视频推送和兴趣推送不同
        setUserVOAndUrl(videos);
        return videos;
    }

    @Override
    public Collection<Video> pushSimilarVideos(Video video) {
        if (ObjectUtils.isEmpty(video.getId()) || ObjectUtils.isEmpty(video.getLabelNames())) {
            return Collections.emptyList();
        }

        List<String> labels = video.buildLabel();
        List<String> labelNames = new ArrayList<>();
        labelNames.addAll(labels);
        labelNames.addAll(labels);

        Set<Long> videoIds = (Set<Long>) interestPushService.listVideoIdByLabels(labels);


        Collection<Video> videos = new ArrayList<>();

        // 去重，将当前视频去除
        videoIds.remove(video.getId());

        if (!ObjectUtils.isEmpty(videoIds)) {
            videos = this.listByIds(videoIds);
            setUserVOAndUrl(videos);
        }

        return videos;
    }

    @Override
    public IPage<Video> getVideoListByPage(BasePage basePage, String YV, String title) {
        LambdaQueryWrapper<Video> wrapper = new LambdaQueryWrapper<>();
        // 根据视频版本号和视频标题模糊查询条件
        wrapper.like(!ObjectUtils.isEmpty(YV), Video::getYv, YV).like(!ObjectUtils.isEmpty(title), Video::getTitle, title);
        // 分页查询出视频
        IPage<Video> page = this.page(basePage.page(), wrapper);

        List<Video> videos = page.getRecords();
        if (ObjectUtils.isEmpty(videos)) return new Page<>();

        List<Long> userIds = new ArrayList<>();
        List<Long> typeIds = new ArrayList<>();

        for (Video video : videos) {
            userIds.add(video.getUserId());
            typeIds.add(video.getTypeId());
        }

        // 根据用户id查询用户，key：userId  value：nickName
        Map<Long, String> userMap = userService.list(new LambdaQueryWrapper<User>().in(User::getId, userIds).select(User::getId, User::getNickName))
                .stream().collect(Collectors.toMap(User::getId, User::getNickName));
        // 根据分类id查询分类，key：typeId  value：typeName
        Map<Long, String> typeMap = typeService.list(new LambdaQueryWrapper<Type>().in(Type::getId, typeIds).select(Type::getId, Type::getName))
                .stream().collect(Collectors.toMap(Type::getId, Type::getName));

        for (Video video : videos) {
            video.setAuditStateName(AuditStatus.getName(video.getAuditStatus()));
            video.setUserName(userMap.get(video.getUserId()));
            video.setTypeName(typeMap.get(video.getTypeId()));
            video.setOpenName(video.getOpen() ? "私密" : "公开");
        }

        return page;
    }

    @Override
    public void auditProcess(Video video) {
        // 放行后
        video.setOpen(false);
        video.setMsg("通过"); // 设置审核信息
        video.setAuditStatus(AuditStatus.SUCCESS.num); // 修改为PASS审核状态
        //  更新视频信息
        this.updateById(video);
        // 将视频推入标签库
        interestPushService.pushSystemStockIn(video);
        // 将视频推入分类库
        interestPushService.pushSystemTypeStockIn(video);
        //将视频推送到该博主的发件箱
        feedService.putOutBoxFeed(video.getUserId(), video.getId(), video.getGmtCreated().getTime());
    }

    @Override
    public Boolean videoViolate(Long id) {
        Video video = this.getById(id);
        if (ObjectUtils.isEmpty(video)) {
            throw new BaseException("指定视频不存在");
        }

        Type type = typeService.getById(video.getTypeId());
        video.setLabelNames(type.getLabelNames());
        // 修改视频信息
        video.setOpen(true); // 修改为私密状态
        video.setMsg("该视频内容违反了邂逅平台的规则，已被下架为私密视频"); // 设置审核信息
        video.setAuditStatus(AuditStatus.PASS.num); // 修改为PASS审核状态

        // 获取视频发布者id，将视频从对应的发件箱中删除
        Long userId = video.getUserId();
        Long videoId = video.getId();
        try {
            // 从标签库中删除
            interestPushService.deleteSystemStockIn(video);
            // 从分类库中删除
            interestPushService.deleteSystemTypeStockIn(video);

            // 获取发布者的粉丝
            Collection<Long> fansIds = followService.getFans(null, userId);
            // 从博主的发件箱中删除
            feedService.deleteOutBoxFeed(userId, fansIds, videoId);

            // 热门视频以及热度排行榜视频
            Calendar calendar = Calendar.getInstance();
            int today = calendar.get(Calendar.DATE);
            // 尝试寻找视频进行删除
            // 从3天内的热门视频中寻找
            redisTemplate.opsForSet().remove(RedisConstant.HOT_VIDEO + today, videoId);
            redisTemplate.opsForSet().remove(RedisConstant.HOT_VIDEO + (today - 1), videoId);
            redisTemplate.opsForSet().remove(RedisConstant.HOT_VIDEO + (today - 2), videoId);
            // 从热度排行榜中删除
            redisTemplate.opsForZSet().remove(RedisConstant.HOT_RANK, videoId);
            // 修改视频
            this.updateById(video);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public List<Video> selectNDaysAgeVideo(long id, int days, int limit) {
        return videoMapper.selectNDaysAgeVideo(id, days, limit);
    }

    @Override
    public Collection<String> getClassifyLabelsById(String types) {
        if (types == null){
            throw new BaseException("分类id不能为空");
        }
        Long typeId = Long.valueOf(types);
        // 查询分类，判断分类是否存在
        Type type = typeService.getOne(new LambdaQueryWrapper<Type>().eq(Type::getId, typeId));
        if (type == null){
            throw new BaseException("传入的分类不存在");
        }

        String[] split = type.getLabelNames().split(",");
        Collection<String> labelList = Stream.of(split).collect(Collectors.toList());
        return labelList;
    }


    /**
     * 判断视频,用户是否收藏、点赞和关注作者过等信息
     * <p>
     * 先从redis中获取数据，如果数据不存在，则从数据库中获取，缓解数据库压力
     *
     * @param key    redis键
     * @param video  视频信息
     * @param userId 用户id
     * @return
     */
    private boolean checkState(String key, Video video, Long userId) {
        Long videoId = video.getId();
        Set<Object> ids = redisCacheUtil.zGet(key);
        if (!ObjectUtils.isEmpty(ids)) {
            if (ids.contains(videoId)) {
                return true;
            }
            return false;
        }

        if (key.equals(RedisConstant.USER_THUMB + userId)) {
            return videoThumbService.thumbUpState(videoId, userId);
        } else if (key.equals(RedisConstant.USER_FAVORITES + userId)) {
            return favoritesService.favoritesState(videoId, userId);
        } else {
            return followService.isFollows(video.getUserId(), userId);
        }
    }

    /**
     * 更新视频点赞数
     *
     * @param video 视频
     * @param value 点赞数
     */
    private void updateThumbUp(Video video, Long value) {
        UpdateWrapper<Video> updateWrapper = new UpdateWrapper<>();
        updateWrapper.setSql("thumb_count = thumb_count + " + value);
        updateWrapper.lambda().eq(Video::getId, video.getId()).eq(Video::getThumbCount, video.getThumbCount());
        this.update(video, updateWrapper);
    }

    /**
     * 更新视频分享数
     *
     * @param video 视频
     * @param value 分享数
     */
    private void updateShare(Video video, Long value) {
        UpdateWrapper<Video> updateWrapper = new UpdateWrapper<>();
        updateWrapper.setSql("share_count = share_count + " + value);
        updateWrapper.lambda().eq(Video::getId, video.getId()).eq(Video::getShareCount, video.getShareCount());
        this.update(video, updateWrapper);
    }

    /**
     * 更新收藏夹数
     *
     * @param video 视频
     * @param value 收藏数
     * @return
     */
    private void updateFavorites(Video video, Long value) {
        UpdateWrapper<Video> updateWrapper = new UpdateWrapper<>();
        updateWrapper.setSql("favorites_count = favorites_count + " + value);
        updateWrapper.lambda().eq(Video::getId, video.getId()).eq(Video::getFavoritesCount, video.getFavoritesCount());
        this.update(video, updateWrapper);
    }

    /**
     * 更新观看次数
     *
     * @param video 视频
     * @param value 观看次数
     */
    private void updateHistory(Video video, Long value) {
        UpdateWrapper<Video> updateWrapper = new UpdateWrapper<>();
        updateWrapper.setSql("history_count = history_count + " + value);
        updateWrapper.lambda().eq(Video::getId, video.getId()).eq(Video::getHistoryCount, video.getHistoryCount());
        this.update(video, updateWrapper);
    }

    /**
     * 设置视频的关联用户数据和视频类型
     *
     * @param videos
     */
    private void setUserVOAndUrl(Collection<Video> videos) {
        if (!ObjectUtils.isEmpty(videos)) {
            Set<Long> userIds = new HashSet<>();
            ArrayList<Long> fileIds = new ArrayList<>();
            for (Video video : videos) {
                userIds.add(video.getUserId());
                fileIds.add(video.getUrl());
                fileIds.add(video.getCover());
            }

            Map<Long, File> fileMap = fileService.listByIds(fileIds).stream().collect(Collectors.toMap(File::getId, Function.identity()));
            Map<Long, User> userMap = userService.listByIds(userIds).stream().collect(Collectors.toMap(User::getId, Function.identity()));

            for (Video video : videos) {
                // 封装返回前端的用户信息
                UserVO userVO = new UserVO();
                User user = userMap.get(video.getUserId());
                userVO.setId(video.getUserId());
                userVO.setNickName(user.getNickName());
                userVO.setDescription(user.getDescription());
                userVO.setSex(user.getSex());
                userVO.setAvatar(user.getAvatar());
                // 设置用户信息
                video.setUser(userVO);
                // 设置视频文件格式
                video.setVideoType(fileMap.get(video.getUrl()).getFormat());
            }
        }
    }
}
